package com.editso.haier.service;


import com.editso.haier.arg.ArgParser;
import com.editso.haier.dao.*;
import com.editso.haier.entity.*;
import com.editso.haier.result.PageData;
import com.editso.haier.result.Result;
import com.editso.haier.result.commodity.DetailsCommodity;
import com.editso.haier.result.commodity.DetailsSecondary;

import com.editso.haier.result.commodity.DetailsTag;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.stereotype.Service;

import javax.persistence.criteria.*;
import java.math.BigDecimal;
import java.util.*;
import java.util.stream.Collectors;


@Service
public class FutureService {

    private TopicDao topicDao;

    private TopicSecondaryDao topicSecondaryDao;

    private CommodityDao commodityDao;

    private TagDao tagDao;

    private CustomerDao customerDao;

    @Autowired
    public void setCustomerDao(CustomerDao customerDao) {
        this.customerDao = customerDao;
    }

    @Autowired
    public void setCommodityDao(CommodityDao commodityDao) {
        this.commodityDao = commodityDao;
    }

    @Autowired
    public void setTopicSecondaryDao(TopicSecondaryDao topicSecondaryDao) {
        this.topicSecondaryDao = topicSecondaryDao;
    }

    @Autowired
    public void setTopicDao(TopicDao topicDao) {
        this.topicDao = topicDao;
    }

    @Autowired
    public void setTagDao(TagDao tagDao) {
        this.tagDao = tagDao;
    }

    /**
     * @see com.editso.haier.controller.user.FutureController#search(String, Integer, Integer, String)
     */
    public Result search(String topicId, String filter, Integer pageOffset, Integer pageSize) {
        return Result.promise(callback -> {
            ArgParser parser = ArgParser.parse(filter);
            Page<Commodity> page = commodityDao.findAll((root, criteriaQuery, builder) -> {
                List<Predicate> predicates = new ArrayList<>();
                Path<Object> secondaryPath = root.join("commoditySecondaries", JoinType.LEFT).get("secondaryId");
                Path<Number> pricePath = root.get("price");
                predicates.add(builder.equal(root.get("topicId"), topicId));
                CriteriaQuery<?> query = criteriaQuery.groupBy(root.get("name"));
                parser.hasMap("sec",
                        tag -> builder.equal(secondaryPath, tag),
                        tags -> predicates.add(builder.and(builder.or(tags.toArray(new Predicate[0])))));
                price(parser, builder, predicates, pricePath);
                parser.hasMap("sort", sort -> {
                    if ("desc".equals(sort))
                        return builder.desc(pricePath);
                    else if ("asc".equals(sort))
                        return builder.asc(pricePath);
                    return null;
                }, order -> query.orderBy(order.toArray(new Order[0])));
                return query.where(predicates.toArray(new Predicate[0])).getRestriction();
            }, PageRequest.of(pageOffset, pageSize));
            callback.resolve(page);
            callback.accept(accept -> Result.success(
                    PageData.from(page, DetailsCommodity::from)
            ));
        }, promise -> {
        });
    }

    /**
     * @see com.editso.haier.controller.user.FutureController#getSecondary(Integer, String)
     * 1
     */
    public Result findSecondary(Integer topicId, String exclude) {
        return Result.promise(callback -> {
            if (topicDao.existsById(topicId)) {
                String[] strings = exclude == null ? new String[0] : exclude.split(":");
                List<CommoditySecondary> secondaryList = topicSecondaryDao.findAll((root, criteriaQuery, criteriaBuilder) -> {
                    Predicate equal = criteriaBuilder.equal(root.get("topicId"), topicId);
                    List<Predicate> predicates = new ArrayList<>();
                    predicates.add(equal);
                    for (String string : strings) {
                        Predicate predicate = criteriaBuilder.notEqual(root.get("secondaryId"), string);
                        predicates.add(predicate);
                    }
                    return criteriaQuery.where(predicates.toArray(new Predicate[0])).getRestriction();
                });
                callback.accept(accept -> Result.success(DetailsSecondary.from(secondaryList)));
                callback.resolve(secondaryList);
            } else {
                callback.error(err -> Result.failure("主题不存在!"));
                callback.reject(null);
            }
        }, promise -> {

        });
    }

    /**
     * @see com.editso.haier.controller.user.FutureController#recommend(String, Integer, String)
     */
    public Result recommend(String type, String filter, Integer count) {
        return Result.promise(callback -> {
            ArgParser parser = ArgParser.parse(filter);
            Page<Commodity> page = commodityDao.findAll((root, criteriaQuery, builder) -> {
                List<Predicate> predicates = new ArrayList<>();
                Path<Object> secondaryPath = root.join("commoditySecondaries", JoinType.LEFT).get("secondaryId");
                Path<Object> tagPath = root.join("tags", JoinType.LEFT).get("id");
                Path<Number> pricePath = root.get("price");
                Path<Object> topicPath = root.get("topicId");
                parser.hasMap("sec",
                        sec -> builder.equal(secondaryPath, sec),
                        secs -> predicates.add(builder.and(builder.or(secs.toArray(emptyPredicate())))));
                parser.hasMap("tag",
                        tag->builder.equal(tagPath, tag),
                        tags->predicates.add(builder.and(builder.or(tags.toArray(emptyPredicate())))));
                price(parser, builder, predicates, pricePath);
                parser.hasMap("topic",
                        topic -> builder.equal(topicPath, topic),
                        topics -> predicates.add(builder.and(topics.toArray(emptyPredicate()))));
                return criteriaQuery.where(predicates.toArray(emptyPredicate())).getRestriction();
            }, PageRequest.of(0, Optional.of(count == 0 ? 1 : count).orElse(6)));
            callback.accept(accept -> Result.success(
                    PageData.from(page, commodity -> DetailsCommodity.from(page)).getItems()
            ));
        }, promise -> {
        });
    }


    private void price(ArgParser parser, CriteriaBuilder builder, List<Predicate> predicates, Path<Number> pricePath) {
        parser.hasMap("price", (o, args) -> {
            Number number = new BigDecimal(String.valueOf(o));
            Predicate predicate = null;
            if (args.size() > 2)
                throw new RuntimeException("金额范围有误");
            if (args.index() == 0)
                predicate = builder.ge(pricePath, number);
            if (args.index() == 1)
                predicate = builder.le(pricePath, number);
            if (args.size() == 1)
                predicate = builder.le(pricePath, number);
            return predicate;
        }, price -> predicates.add(builder.and(price.toArray(new Predicate[0]))));
    }

    private Predicate[] emptyPredicate(){
        return new Predicate[0];
    }

    public Result getTags() {
        return Result.promise(callback->{
            callback.resolve(DetailsTag.from(tagDao.findAll()));
            callback.accept(Result::success);
        });
    }


    public Result related(String assetsName, Integer count) {
        return Result.promise(callback -> {
            Customer customer = customerDao.findByName(assetsName).orElse(null);
            List<Assets> collect = customer.getAssets().stream().limit(count).collect(Collectors.toList());
            callback.accept(r->Result.success(collect));

        });
    }
}
